import os
import requests
import uuid
from flask import Flask, render_template, request, jsonify
from dotenv import load_dotenv # ⬅️ 导入 load_dotenv

import ffmpeg     
import websocket  
import datetime
import hashlib
import base64
import hmac
import json 
from urllib.parse import urlencode
import time
import ssl
from threading import Thread

# 【【【 V13 核心：加载 .env 文件 】】】
# 这会加载你本地 .env 文件中的变量 (如果存在)
# 在 Docker 中，它会读取 docker run -e 传入的变量
load_dotenv() 

app = Flask(__name__)
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0

# ⬇️ ⬇️ ⬇️ 【【【【 终极修复：从环境变量读取 Key 】】】】 ⬇️ ⬇️ ⬇️
#    我们不再硬编码 Key，而是从 os.environ.get() 读取
#    (你本地需要创建一个 .env 文件来存它们, .gitignore 已忽略)

# 阿里云 LLM (助教有 Key)
LLM_API_KEY = os.environ.get('LLM_API_KEY')
LLM_API_ENDPOINT = "https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation"

# 科大讯飞 (助教没有 Key, 必须提供)
XF_APPID = os.environ.get('XF_APPID')
XF_APIKey = os.environ.get('XF_APIKey')
XF_APISecret = os.environ.get('XF_APISecret')

# supava APIkey，前端使用
SUPABASE_URL = os.environ.get('SUPABASE_URL')
SUPABASE_KEY = os.environ.get('SUPABASE_KEY')
# 高德地图 APIkey，前端使用
AMAP_KEY = os.environ.get('AMAP_KEY')
AMAP_JSCODE = os.environ.get('AMAP_JSCODE')

# ⬆️ ⬆️ ⬆️ 【【【【 终极修复：从环境变量读取 Key 】】】】 ⬆️ ⬆️ ⬆️


@app.route('/')
def home():
    # ⬇️ ⬇️ ⬇️ 【【【【 V15 核心：将 Key 注入模板 】】】】 ⬇️ ⬇️ ⬇️
    return render_template('index.html', 
        SUPABASE_URL=SUPABASE_URL,
        SUPABASE_KEY=SUPABASE_KEY,
        AMAP_KEY=AMAP_KEY,
        AMAP_JSCODE=AMAP_JSCODE
    )
    # ⬆️ ⬆️ ⬆️ 【【【【 V15 核心：将 Key 注入模板 】】】】 ⬆️ ⬆️ ⬆️

# (V8 终极方案：核心修改)
@app.route('/api/generate-plan', methods=['POST'])
def api_generate_plan():
    # ... (V8 Prompt, 完全不变, 已折叠) ...
    user_prompt = request.json['prompt_from_user']
    print(f"收到的用户需求: {user_prompt}")
    system_prompt = f"""
你是一个专业、友好、富有创意的旅行规划师。
用户的需求是：'{user_prompt}'。
【格式要求】：
你【必须】将你的【所有】回答，严格组织成一个【单一的、完整的 JSON 对象】。
【不要】在 JSON 之外返回任何解释性文本或 markdown 标记 (例如 ```json ... ```)。
你的 JSON 结构【必须】包含 "plan_title", "estimated_budget", 和 "itinerary"。
"itinerary" 中的每个 "activities" 【必须】包含 "description" 和 "location_name"。
"location_name" 必须是一个【可用于地图定位的详细地址】。
JSON 结构模板如下（请根据 '{user_prompt}' 动态填充内容）：
{{
  "plan_title": "（AI 根据 user_prompt 创造的标题）",
  "estimated_budget": {{ "total": (AI 估算的总金额, 整数), "categories": [ {{ "item": "（例如：机票）", "amount": (金额, 整数) }} ] }},
  "itinerary": [ {{ "day": 1, "day_title": "（AI 创造的当日主题）", "activities": [ {{ "description": "（AI 创造的活动描述）", "location_name": "（AI 创造的、可定位的详细地址）" }} ] }} ]
}}
"""
    payload = { 'model': 'qwen-turbo', 'input': { 'prompt': system_prompt }, 'parameters': {} }
    headers = { 'Authorization': f'Bearer {LLM_API_KEY}', 'Content-Type': 'application/json' }
    try:
        response = requests.post(LLM_API_ENDPOINT, headers=headers, json=payload)
        response.raise_for_status() 
        llm_result_string = response.json()['output']['text']
        llm_json_data = json.loads(llm_result_string)
        return jsonify(llm_json_data) 
    except json.JSONDecodeError as e:
        print(f"AI 未返回标准JSON，解析失败: {e}"); print(f"AI的原始回复 (这很有用): {llm_result_string}")
        error_json = { "plan_title": "AI 规划失败", "itinerary": [{"day": 1, "day_title": "AI 未能按要求返回标准 JSON 格式", "activities": [{"description": f"AI原始回复: {llm_result_string}", "location_name": "N/A"}]}], "estimated_budget": {"total": 0, "categories": []} }
        return jsonify(error_json), 500
    except Exception as e:
        print(f"调用LLM API失败: {e}")
        error_json = { "plan_title": "AI 规划失败", "itinerary": [{"day": 1, "day_title": f"调用API时出错: {e}", "activities": []}], "estimated_budget": {"total": 0, "categories": []} }
        return jsonify(error_json), 500

# --- 讯飞 WebSocket (保持不变, 已折叠) ---
transcription_result = ""; transcription_complete = False
class Ws_Param(object):
    def __init__(self, APPID, APIKey, APISecret, AudioFile):
        self.APPID = APPID; self.APIKey = APIKey; self.APISecret = APISecret
        self.AudioFile = AudioFile; self.CommonArgs = {"app_id": self.APPID}
        self.BusinessArgs = {"domain": "iat", "language": "zh_cn", "accent": "mandarin", "vinfo":1,"vad_enable":True,"dwa":"wpgs"} # ⬅️ 已修复
    def create_url(self):
        url = 'wss://ws-api.xfyun.cn/v2/iat'; now = datetime.datetime.now()
        date = datetime.datetime.strftime(now, '%a, %d %b %Y %H:%M:%S GMT'); date = date.encode('utf-8')
        signature_origin = "host: " + "ws-api.xfyun.cn" + "\n"; signature_origin += "date: " + date.decode('utf-8') + "\n"
        signature_origin += "GET " + "/v2/iat " + "HTTP/1.1"; signature_origin = signature_origin.encode('utf-8')
        signature_sha = hmac.new(self.APISecret.encode('utf-8'), signature_origin, digestmod=hashlib.sha256).digest()
        signature_sha = base64.b64encode(signature_sha).decode('utf-8')
        authorization_origin = "api_key=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"" % (
            self.APIKey, "hmac-sha256", "host date request-line", signature_sha)
        authorization = base64.b64encode(authorization_origin.encode('utf-8')).decode('utf-8')
        v = {"authorization": authorization, "date": date.decode('utf-8'), "host": "ws-api.xfyun.cn"}
        url = url + '?' + urlencode(v); return url
def on_message(ws, message):
    global transcription_result, transcription_complete
    try:
        message = json.loads(message)
        data = message.get('data', {})

        # --- 核心修复 V15.2 ---

        # 1. 检查这是不是【最后】一帧
        if data.get('status', 1) == 2:
            transcription_complete = True
            # (我们【不】从 status=2 的帧里提取文本)
            # (transcription_result 保持在 status=1 时的最后状态)
            print(f"讯飞识别完成。最终结果: {transcription_result}")

        # 2. 检查这是不是【中间】帧 (status 0 或 1)
        else:
            result = data.get('result', {})
            current_sentence = ""
            for i in result.get('ws', []):
                current_sentence += i.get('cw', [{}])[0].get('w', '')

            # 3. 【覆盖】
            #    如果中间帧有内容，我们就用它覆盖
            if current_sentence:
                transcription_result = current_sentence
                print(f"讯飞中间结果: {transcription_result}") # (我加了一行日志)

        # --- 核心修复结束 ---

    except Exception as e:
        print("处理消息出错:", e)
def on_error(ws, error):
    global transcription_complete; print("WebSocket 错误:", error); transcription_complete = True
def on_close(ws, *args): print("WebSocket 已关闭")
def on_open(ws, wsParam):
    def run(*args):
        frameSize = 8000; intervel = 0.04; status = 0
        with open(wsParam.AudioFile, "rb") as f:
            while True:
                buf = f.read(frameSize)
                if not buf: status = 2
                if status == 0:
                    d = {"common": wsParam.CommonArgs, "business": wsParam.BusinessArgs,
                         "data": {"status": 0, "format": "audio/L16;rate=16000",
                                  "audio": str(base64.b64encode(buf), 'utf-8'), "encoding": "raw"}}
                    status = 1
                elif status == 1:
                    d = {"data": {"status": 1, "format": "audio/L16;rate=16000",
                                  "audio": str(base64.b64encode(buf), 'utf-8'), "encoding": "raw"}}
                else:
                    d = {"data": {"status": 2, "format": "audio/L16;rate=16000",
                                  "audio": "", "encoding": "raw"}}
                    ws.send(json.dumps(d)); break
                ws.send(json.dumps(d)); time.sleep(intervel)
    Thread(target=run).start()

# --- 讯飞 API 路由 (保持不变, 已折叠) ---
@app.route('/api/transcribe', methods=['POST'])
def api_transcribe():
    global transcription_result, transcription_complete
    transcription_result = ""; transcription_complete = False
    if 'audio_data' not in request.files: return jsonify({'error': '没有音频文件'}), 400
    audio_file = request.files['audio_data']
    base_filename = f"temp_audio_{uuid.uuid4()}"
    webm_filepath = f"./{base_filename}.webm"; wav_filepath = f"./{base_filename}.wav" 
    try:
        audio_file.save(webm_filepath); print(f"临时文件已保存: {webm_filepath}")
        print("开始转码..."); ffmpeg.input(webm_filepath).output(wav_filepath, ar=16000, ac=1).run(); print(f"转码完成: {wav_filepath}")
        
        # 【【【 关键检查 】】】
        if not XF_APPID or not XF_APIKey or not XF_APISecret:
            raise Exception("讯飞 API Key 未配置 (XF_APPID, XF_APIKey, XF_APISecret)")
            
        wsParam = Ws_Param(APPID=XF_APPID, APIKey=XF_APIKey, APISecret=XF_APISecret, AudioFile=wav_filepath) 
        websocket.enableTrace(False); ws_url = wsParam.create_url()
        ws = websocket.WebSocketApp(ws_url, on_message=on_message, on_error=on_error, on_close=on_close)
        ws.on_open = lambda ws: on_open(ws, wsParam)
        ws_thread = Thread(target=ws.run_forever, kwargs={"sslopt": {"cert_reqs": ssl.CERT_NONE}}); ws_thread.start()
        timeout = 15
        while not transcription_complete and timeout > 0: time.sleep(1); timeout -= 1
        if not transcription_complete: ws.close(); raise Exception("语音识别超时")
        print(f"识别结果: {transcription_result}"); return jsonify({'text': transcription_result})
    except Exception as e:
        print(f"语音识别处理异常: {e}")
        if "ffmpeg" in str(e).lower() or "No such file or directory" in str(e):
             print("【【【【 FFMPEG 错误：】】】】"); print("很可能是你没有安装 FFmpeg, 或者 ffmpeg.exe 不在项目根目录。"); print("请返回 步骤 4.4！")
        return jsonify({'error': str(e)}), 500
    finally:
        if os.path.exists(webm_filepath): os.remove(webm_filepath); print(f"临时文件已删除: {webm_filepath}")
        if os.path.exists(wav_filepath): os.remove(wav_filepath); print(f"临时文件已删除: {wav_filepath}")

# --- (修改) ---
if __name__ == '__main__':
    # 删除了 debug=True，Gunicorn 会处理
    app.run(host='0.0.0.0', port=5000)